Skip to content

Keep chat streams alive across navigation#74

Merged
mcintyre94 merged 6 commits intomainfrom
discuss-sprite-service-management-6e54d330
Mar 15, 2026
Merged

Keep chat streams alive across navigation#74
mcintyre94 merged 6 commits intomainfrom
discuss-sprite-service-management-6e54d330

Conversation

@mcintyre94
Copy link
Owner

Summary

  • Fix .reconnecting.streaming transition: processServiceStream only upgraded status from .connecting to .streaming, so a reconnect that was actively receiving data stayed stuck showing "reconnecting" the whole time. Now also transitions from .reconnecting.

  • Skip polling loop when service already stopped: reconnectIfNeeded was fetching service status but discarding the result. Now passes it as serviceAlreadyStopped to reconnectToServiceLogs, which exits after one log fetch instead of making a redundant status API call and re-entering the loop.

  • App-wide ChatSessionManager: Previously ChatViewModel was recreated on every chat switch, cancelling the active stream and requiring a reconnect on return. ChatSessionManager is a @MainActor @Observable cache keyed by chat UUID, injected into the SwiftUI environment at the app root. VMs (and their streams) now survive switching chats or navigating between sprites — connections are only torn down on explicit actions (interrupt, clearing all chats).

Test plan

  • Send a message to Claude, switch to another chat mid-response, switch back — should show the response streaming without a reconnect
  • Send a message to Claude, navigate to a different sprite, navigate back — stream should still be live
  • Background the app while Claude is running, return — should reconnect all cached VMs
  • Switch to a chat where Claude already finished while you were away — should catch up on missed events in a single log fetch without entering the polling loop
  • Verify status shows .streaming (not .reconnecting) once data is flowing during a reconnect
  • Delete all chats via "Start Fresh" — should cleanly detach all cached VMs first

🤖 Generated with Claude Code

@mcintyre94 mcintyre94 force-pushed the discuss-sprite-service-management-6e54d330 branch 2 times, most recently from 7619094 to 72d9737 Compare March 15, 2026 00:33
@claude
Copy link

claude bot commented Mar 15, 2026

Bug: remove(chatId:) is never called when individual chats are deleted. ChatSessionManager.remove(chatId:modelContext:) is defined to detach a VM and evict it from the cache, but no call site invokes it. Individual chat deletion goes through chatListViewModel.deleteChat() in SpriteNavigationPanel and ChatSwitcherSheet, but neither was updated to call chatSessionManager.remove(). Result: when a user deletes a chat, ChatViewModel stays in cache with WebSocket stream active, leaking a live network connection. Fix: call chatSessionManager.remove alongside deleteChat in both locations. Also missing: unit tests for ChatSessionManager and tests for .reconnecting to .streaming status transitions as required by CLAUDE.md.

mcintyre94 and others added 5 commits March 15, 2026 17:49
Previously processServiceStream only upgraded status from .connecting to
.streaming, so a reconnect that was actively receiving data stayed stuck
on .reconnecting the whole time. Extend the same transition to also fire
from .reconnecting so the UI reflects that streaming is live.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
reconnectIfNeeded now captures the service status it fetches rather than
discarding it. If the service is already stopped, we pass serviceAlreadyStopped
to reconnectToServiceLogs so it exits after a single log fetch instead of
making a redundant status API call and looping.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Previously ChatViewModel was recreated on every chat switch, cancelling
the active stream and requiring a reconnect on return. ChatSessionManager
is an @observable cache keyed by chat UUID, injected app-wide, so VMs
(and their streams) survive switching chats or navigating between sprites.

switchToChat now looks up the cached VM instead of creating a new one.
The scenePhase handler resumes all cached VMs on foreground, not just the
active one. clearAllChats detaches all VMs before wiping the chat list.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Call chatSessionManager.remove() on both closeChat and deleteChat in
  SpriteNavigationPanel and ChatSwitcherSheet, so cached VMs are
  detached and evicted immediately rather than leaking with an active
  WebSocket stream
- Remove .disabled(isConnecting) on the new chat button — switching
  chats no longer cancels streams so the guard is unnecessary
- Add ChatSessionManagerTests covering cache hit, eviction, detachAll,
  and isStreaming behaviour
- Add runReconnectLoop status transition test (.reconnecting → .idle)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
When reconnecting to a service, runReconnectLoop was using
messages.last(where: .assistant) which could find an assistant message
from a *previous* exchange and write the new response into it — placing
it before the most recent user message.

Fix: only reuse an existing assistant message if it's already at the
tail of the messages array. Since each user message gets a fresh service,
if the last message is a user message a new assistant message must always
be created after it.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@mcintyre94 mcintyre94 force-pushed the discuss-sprite-service-management-6e54d330 branch from a7c33be to 9e403e0 Compare March 15, 2026 17:49
When the app is killed after a user message is persisted but before the
service is created on the Sprite, reconnectIfNeeded would silently return
leaving a dangling user bubble with no response. Now it detects a trailing
user message and restores it to the input box as a draft instead.

Also handles the edge case where lastSessionComplete is true but a new user
message was appended before saveSession(isComplete:false) ran.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@mcintyre94 mcintyre94 merged commit a42bb5d into main Mar 15, 2026
2 checks passed
@mcintyre94 mcintyre94 deleted the discuss-sprite-service-management-6e54d330 branch March 15, 2026 18:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant